<?php
/**
 * Created by PhpStorm.
 * User: cc
 * Date: 2023/2/9
 * Time: 11:07 AM
 */

namespace app\common\service;

use app\common\lib\Util;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\SignatureInvalidException;

class ApplePay
{
    private static $instance;
    public static $ticketPassword = '0d38703ff6d04624becd2afc501e3831';

    // 禁止被实例化
    private function __construct()
    {

    }

    // 禁止clone
    private function __clone()
    {

    }

    /**
     * 实例化自己并保存到$instance中，已实例化则直接调用
     * @return ApplePay
     */
    public static function getInstance()
    {
        if (empty(self::$instance))
        {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * jwt验签
     * @param $jws
     * @return array
     * @throws \Exception
     */
    public function validateJwt($jws)
    {
//        $jws = json_decode($jws, true)['signedPayload'];
        //Jws 根据 . 分割的三段字符串
        $components = explode('.', $jws);
        if (count($components) !== 3)
        {
            throw new \RuntimeException('JWS string must contain 3 dot separated component.');
        }

        $header = base64_decode($components[0]);
        $headerJson = json_decode($header, true);
        $this->validateAppleRootCA($headerJson);
        return $this->decodeCertificate($jws, $headerJson, 0);
    }

    /**
     * 验证证书
     * @param $headerJson
     * @return void
     * @throws \Exception
     */
    private function validateAppleRootCA($headerJson)
    {
        $lastIndex = count($headerJson['x5c']) - 1;
        $certificate = $this->getCertificate($headerJson, $lastIndex);

        // 去苹果官方下载根证书（第4个） https://www.apple.com/certificateauthority/. 本机终端转一下格式，命令如下: openssl x509 -inform der -in AppleRootCA-G3.cer -out AppleRootCA-G3.pem
        $Path = EXTEND_PATH . '/ApplePay/AppleRootCA-G3.pem';
        $appleRootCA = file_get_contents($Path);
        if ($certificate != $appleRootCA)
        {
            throw new \Exception('jws invalid');
        }
    }

    /**
     * 获取证书
     * @param $headerJson
     * @param $certificateIndex
     * @return string
     */
    private function getCertificate($headerJson, $certificateIndex)
    {
        $certificate = '-----BEGIN CERTIFICATE-----' . PHP_EOL;
        $certificate .= chunk_split($headerJson['x5c'][$certificateIndex], 64, PHP_EOL);
        $certificate .= '-----END CERTIFICATE-----' . PHP_EOL;

        return $certificate;
    }

    /**
     * 解码
     * @param $jwt
     * @param $headerJson
     * @param $certificateIndex
     * @return array
     * @throws \Exception
     */
    private function decodeCertificate($jwt, $headerJson, $certificateIndex = 0)
    {
        $certificate = $this->getCertificate($headerJson, 0);

        $cert_object = openssl_x509_read($certificate);
        $pkey_object = openssl_pkey_get_public($cert_object);
        $pkey_array = openssl_pkey_get_details($pkey_object);
        $publicKey = $pkey_array['key'];

        try
        {
            $jwsDecoded = JWT::decode($jwt, new Key($publicKey, 'ES256'));
            return (array)$jwsDecoded;
        }
        catch (SignatureInvalidException $e)
        {
            throw new \RuntimeException('signature invalid');
        }
    }

    /**
     * 访问苹果 App Store Server API 的token
     * @return string
     */
    private static function getToken()
    {
        $teamId = '69a6de87-aba7-47e3-e053-5b8c7c11a4d1';
        $keyId = 'FDG4QQNM5S';
        $bid = 'com.liuhe.xzns';
        $privateKey = file_get_contents(EXTEND_PATH . '/ApplePay/AuthKey_FDG4QQNM5S.p8');
        $header = [
            'alg' => 'ES256',
            'kid' => $keyId,
            'typ' => 'JWT'
        ];
        $payload = [
            'iss' => $teamId,
            'iat' => time(),// 令牌起始时间，单位秒
            'exp' => time() + 1200, // 令牌失效时间，注意最长不超过20分钟
            'aud' => 'appstoreconnect-v1',// 固定
            'bid' => $bid,
        ];
        return JWT::encode($payload, $privateKey, 'ES256', $keyId, $header);
    }

    /**
     * 发送请求苹果 App Store Server API
     * @param bool $isSandBox 是否是沙箱模式
     * @param string $url
     * @return mixed
     */
    public static function sendUrl($isSandBox, $url, $methodType = 'GET')
    {
        $jwt = self::getToken();
        if ($isSandBox)
        {
            $url = "https://api.storekit-sandbox.itunes.apple.com/inApps/{$url}";
        } else
        {
            $url = "https://api.storekit.itunes.apple.com/inApps/{$url}";
        }

        $response = Util::curlRequest($methodType, $url, [], [
            'Authorization: Bearer ' . $jwt,
            'Content-Type: application/json'
        ]);
        return json_decode($response, true);
    }

    /**
     * 验证票据
     * @param $isSandBox
     * @param $ticket
     * @param string $password 苹果自动续订商品验证票据需要共享密钥
     * @param bool $markUsed 标记票据已被使用
     * @return mixed
     */
    public static function verificationTicket($isSandBox, $ticket, string $password = '', bool $markUsed = false)
    {
        //苹果支付参数去除参数多余字符
        $receiptData = str_replace(["\\r\\n", "\\r", "\\n"], "", $ticket);
        $ticket = str_replace(' ', '+', $receiptData);
        if ($isSandBox)
        {
            // 若为审核模式，使用沙箱地址
            $notify_url = 'https://sandbox.itunes.apple.com/verifyReceipt';
        } else
        {
            // 非审核模式，使用正式地址
            $notify_url = 'https://buy.itunes.apple.com/verifyReceipt';
        }
        // 票据处理成json格式,如果PHP 直接使用 json_encode(**) 的话，会导致 receiptString 中出现的 \ 进行进一步的处理，变成 \\，从而导致验证出现 21002 的错误

        if ($markUsed)
        {
            if ($password)
            {
                $tmp = '{"receipt-data":"' . $ticket . '","password":"' . $password . '","status":21006}';
            } else
            {
                $tmp = '{"receipt-data":"' . $ticket . '","status":21006}';
            }
        } else if ($password)
        {
            $tmp = '{"receipt-data":"' . $ticket . '","password":"' . $password . '"}';
        } else
        {
            $tmp = '{"receipt-data":"' . $ticket . '"}';
        }
        $res = curl($notify_url, 1, $tmp);
        return json_decode($res, true);
    }
}
